//多边形水面
import * as Cesium from 'cesium';
import { ESCesiumViewer } from '../../../../ESCesiumViewer';
import { createNextAnimateFrameEvent, debounce, Destroyable, Event, extendClassProps, ObjResettingWithEvent, reactArrayWithUndefined, reactJsonWithUndefined, reactPositions, SceneObjectKey, UniteChanged } from 'xbsj-base';
import { ESJColor, ESSceneObject, getDistancesFromPositions, getMinMaxCorner } from "earthsdk3";
import { createPolygonHierarchy, flyTo, getXbsjWaterMaterial, positionFromCartesian, positionsToLocalPositions, positionsToUniqueCartesians, toColor } from '../../../../utils';
import { PolygonHierarchyType } from '../../../../ESJTypesCzm';
export type WaterAttribute = {
    waterColor?: ESJColor,
    frequency?: number,
    waveVelocity?: number,
    amplitude?: number,
    specularIntensity?: number,
    flowDirection?: number,
    flowSpeed?: number,
}

export class CzmWater extends Destroyable {
    private _primitive?: Cesium.Primitive | Cesium.GroundPrimitive;

    private _flyToEvent = this.dv(new Event<[duration: number]>());
    get flyToEvent() { return this._flyToEvent; }
    flyTo(duration: number) { this._flyToEvent.emit(duration); }

    private _updateBoundingSphereEvent = this.ad(new Event<[boolean]>());
    get updateBoundingSphereEvent() { return this._updateBoundingSphereEvent; }
    updateBoundingSphere(update: boolean) { this._updateBoundingSphereEvent.emit(update); }

    constructor(czmViewer: ESCesiumViewer, id?: SceneObjectKey) {
        super();
        const viewer = czmViewer.viewer;
        if (!viewer) {
            console.warn(`viewer is undefined!`);
            return;
        }

        const boundingSphere = new Cesium.BoundingSphere();
        const removePrimitive = () => {
            this._primitive && viewer.scene.primitives.remove(this._primitive);
            this._primitive = undefined;
        };
        this.d(removePrimitive);

        const getEnvPath = (url: string) => {
            return ESSceneObject.context.getStrFromEnv(url)
        }

        const createPrimitive = () => {
            this._primitive = new Cesium[!this.ground ? "Primitive" : "GroundPrimitive"]({
                compressVertices: false,
                asynchronous: false,
                allowPicking: this.allowPicking,
                geometryInstances: getPolygonInstance(),
            })
            //@ts-ignore
            Cesium.Primitive.prototype && (this._primitive.ESSceneObject = id)
            viewer.scene.primitives.add(this._primitive);
        }
        const updateAppearance = () => {
            const normalMap = getEnvPath(this.normalMap ?? '${earthsdk3-assets-script-dir}/assets/img/water/waterNormalsSmall.jpg')
            const uniforms: any = {
                baseWaterColor: toColor(this.waterColor ?? [0.25, 0.6, 1, 0.7]),
                normalMap,
                frequency: this.frequency,
                animationSpeed: this.animationSpeed,
                amplitude: this.amplitude,
                specularIntensity: this.specularIntensity,
            }
            this.blendColor && (uniforms.blendColor = toColor(this.blendColor))
            this.specularMap && (uniforms.specularMap = getEnvPath(this.specularMap))
            do {
                if (!this.points || this.points.length < 1) break;
                const { maxPos, minPos } = getMinMaxCorner(this.points);
                const width = getDistancesFromPositions([minPos, [maxPos[0], maxPos[1], minPos[2]]], "NONE")[0];
                const height = getDistancesFromPositions([minPos, [minPos[0], maxPos[1], maxPos[2]]], "NONE")[0];
                if (this.baseWaterImage) {
                    this.baseWaterImage.url && (uniforms.baseWaterImage = getEnvPath(this.baseWaterImage.url));
                    this.baseWaterImage.uDis && (uniforms.repeatX = width / this.baseWaterImage.uDis);
                    this.baseWaterImage.vDis && (uniforms.repeatY = height / this.baseWaterImage.vDis);
                }
                uniforms.flowSpeed = this.flowSpeed / (this.baseWaterImage?.uDis ?? width);
            } while (false);
            if (this._primitive && this._primitive.appearance) {
                this._primitive.appearance.material.uniforms = Object.assign({}, this._primitive.appearance.material.uniforms, uniforms);
                return;
            }
            const appearance = new Cesium.EllipsoidSurfaceAppearance({
                material: getXbsjWaterMaterial(uniforms),
                aboveGround: true,
            });
            this._primitive && (this._primitive.appearance = appearance);
        }
        const eventAppearance = this.ad(createNextAnimateFrameEvent(
            this.baseWaterImageChanged,
            this.waterColorChanged,
            this.blendColorChanged,
            this.specularMapChanged,
            this.flowSpeedChanged,
            this.normalMapChanged,
            this.frequencyChanged,
            this.animationSpeedChanged,
            this.amplitudeChanged,
            this.specularIntensityChanged,
        ))
        this.ad(eventAppearance.don(updateAppearance));

        const updateBoundingSphere = (boundingSphere: Cesium.BoundingSphere) => {
            if (!this.points) return;
            const cartesians = positionsToUniqueCartesians(this.points);
            if (cartesians.length < 3) {
                boundingSphere.radius = -1;
                return;
            }
            Cesium.BoundingSphere.fromPoints(cartesians, boundingSphere);
        }
        const getPolygonInstance = () => {
            if (!this.points || this.points.length < 3) return undefined
            const { minPos } = getMinMaxCorner(this.points);
            const [localPositions] = positionsToLocalPositions({ originPosition: minPos }, this.points);
            const { minPos: localMinPos, maxPos: localMaxPos } = getMinMaxCorner(localPositions);
            const width = localMaxPos[0] - localMinPos[0];
            const length = localMaxPos[1] - localMinPos[1];
            const hierarchy = { positions: this.points } as PolygonHierarchyType;
            const geometry = new Cesium.PolygonGeometry({
                polygonHierarchy: createPolygonHierarchy(hierarchy),
                vertexFormat: Cesium.VertexFormat.DEFAULT,
                stRotation: this.stRotation && Cesium.Math.toRadians(this.stRotation),
                perPositionHeight: true,
                //@ts-ignore
                attributes: {
                    st: new Cesium.GeometryAttribute({
                        componentDatatype: Cesium.ComponentDatatype.FLOAT,
                        componentsPerAttribute: 2,
                        values: new Float32Array(localPositions.map(item => {
                            return [
                                Cesium.Math.clamp(item[0] / width, 0, 1),
                                Cesium.Math.clamp(item[1] / length, 0, 1)
                            ]
                        }).flat())
                    })
                }
            });
            const polygonInstance = new Cesium.GeometryInstance({
                id: this,
                geometry
            });
            return polygonInstance
        }
        const updateShow = () => {
            this._primitive && (this._primitive.show = this.show ?? true);
        }
        const eventShow = this.ad(createNextAnimateFrameEvent(
            this.showChanged,
        ))
        this.ad(eventShow.don(updateShow))
        const reset = this.ad(() => {
            removePrimitive();
            createPrimitive();
            updateShow();
            updateAppearance();
            updateBoundingSphere(boundingSphere);
        });
        reset();
        const event = this.ad(createNextAnimateFrameEvent(
            this.allowPickingChanged,
            this.groundChanged,
        ))
        this.ad(event.don(reset));
        this.ad(new ObjResettingWithEvent(this.updateBoundingSphereEvent, (newVal) => {
            const destroyable = new Destroyable();
            if (!newVal) {
                reset();
                destroyable.ad(this.pointsChanged.don(reset));
            } else {
                destroyable.ad(viewer.scene.preUpdate.addEventListener(() => {
                    const camera = viewer.camera;
                    const cullingVolume = viewer.camera.frustum.computeCullingVolume(camera.position, camera.direction, camera.up);
                    const intersect = cullingVolume.computeVisibility(boundingSphere);
                    if (intersect === Cesium.Intersect.OUTSIDE) {
                        reset();
                    }
                }));
            }
            return destroyable;
        }))
        const updateGeometryInstances = () => {
            if (!this.points || this.points.length < 3) {
                reset();
                return;
            }
            if (this._primitive instanceof Cesium.Primitive) {
                //@ts-ignore
                this._primitive._state = 3; // 关键
                //@ts-ignore
                this._primitive._appearance = undefined; // 关键
            } else {
                //@ts-ignore
                this._primitive._primitive = undefined;
            }
            //@ts-ignore
            this._primitive.geometryInstances = getPolygonInstance();
            //@ts-ignore
            this._primitive._recomputeBoundingSpheres = true;
        }
        const eventGeometryInstances = this.dv(createNextAnimateFrameEvent(
            this.pointsChanged,
            this.stRotationChanged,
        ));
        this.d(eventGeometryInstances.don(updateGeometryInstances));
        this.d(this.flyToEvent.don(duration => {
            if (boundingSphere.radius > 0) {
                const target = positionFromCartesian(boundingSphere.center);
                target && flyTo(viewer, target, boundingSphere.radius * 4.0, undefined, duration);
            }
        }));
    }
}

export namespace CzmWater {
    export const createDefaultProps = () => ({
        show: true,
        points: reactPositions(undefined),
        allowPicking: true,

        waterColor: [0.1497, 0.165, 0.0031, 1] as ESJColor,
        blendColor: reactArrayWithUndefined<[number, number, number, number] | undefined>(undefined),
        specularMap: undefined as string | undefined,
        normalMap: '${earthsdk3-assets-script-dir}/assets/img/water/waterNormalsSmall.jpg',
        frequency: 1000,
        animationSpeed: 0.005,
        amplitude: 10,
        specularIntensity: 0.3,
        baseWaterImage: reactJsonWithUndefined<{ [xx: string]: any }>(undefined),
        flowSpeed: 0,
        stRotation: undefined as number | undefined,
        ground: undefined as boolean | undefined,
    });
}
extendClassProps(CzmWater.prototype, CzmWater.createDefaultProps);
export interface CzmWater extends UniteChanged<ReturnType<typeof CzmWater.createDefaultProps>> { }

